<?php
/**
 * Controller_Response
 * 
 * 作者:  姚刚 (yaogang@the9sense.com)
 * 创建时间: 2011-02-24 00:15:44
 * 修改记录: 
 * 
 * $Id: Response.php 2 2011-03-08 01:31:19Z coosuu@gmail.com $
 */

class Controller_Response {

    /**
     * 响应内容
     *
     * @var array
     */
    protected $_body = array();

    /**
     * 使用数组形式设置的header
     *
     * @var array
     */
    protected $_headers = array();

    /**
     * 使用字符串直接设置的header数组
     *
     * @var array
     */
    protected $_headersRaw = array();

    /**
     * 响应的HTTP代码
     *
     * @var int
     */
    protected $_httpResponseCode = 200;

    /**
     * _isRedirect
     * 标志当前是否为跳转
     *
     * @var boolean
     */
    protected $_isRedirect = false;

    /**
     * 如果headers已经发送，是否抛出异常
     *
     * @see canSendHeaders()
     * @var boolean
     */
    public $headersSentThrowsException = true;

    /**
     * _responseSent
     * 响应是否已发送
     *
     * @var boolean
     */
    protected $_responseSent = false;

    private static $_instance = null;

    public function __construct() {

    }

    public static function &getInstance() {

        if (!self::$_instance) {
            self::$_instance = new self;
        }
        return self::$_instance;
    }

    /**
     * 格式化header name
     *
     * Normalizes a header name to X-Capitalized-Names
     *
     * @param  string $name
     * @return string
     */
    protected function _normalizeHeader($name) {

        $filtered = str_replace(array('-', '_'), ' ', (string) $name);
        $filtered = ucwords(strtolower($filtered));
        $filtered = str_replace(' ', '-', $filtered);
        return $filtered;
    }

    /**
     * setHeader
     * 设置头，例如setHeader('Content-type', 'text/html');
     *
     * @param string $name
     * @param string $value
     * @param boolean $replace
     * @return object Controller_Response
     */
    public function setHeader($name, $value, $replace = false) {

        $this->canSendHeaders(true);
        $name  = $this->_normalizeHeader($name);
        $value = (string) $value;

        if ($replace) {
            foreach ($this->_headers as $key => $header) {
                if ($name == $header['name']) {
                    unset($this->_headers[$key]);
                }
            }
        }

        $this->_headers[] = array(
            'name'    => $name,
            'value'   => $value,
            'replace' => $replace
        );

        return $this;
    }

    /**
     * setRedirect
     * 设置跳转URL
     *
     * @param string $url
     * @param int $code
     * @return object Controller_Response
     */
    public function setRedirect($url, $code = 302) {

        $this->canSendHeaders(true);
        $this->setHeader('Location', $url, true)
             ->setHttpResponseCode($code);

        return $this;
    }

    /**
     * setCookie
     * 设置cookie
     *
     * @param  string $name
     * @param  string $value
     * @param  integer $expired
     * @param  string $path
     * @param  string $domain
     * @return void
     */
    public function setCookie($name, $value, $expired = 0, $path = '/', $domain = null) {

        setcookie($name, $value, $expired, $path, $domain);
        $_COOKIE[$name] = $value;
        return $this;
    }

    /**
     * isRedirect
     * 判断是否为跳转
     *
     * @return boolean
     */
    public function isRedirect() {

        return $this->_isRedirect;
    }

    /**
     * getHeaders
     * 获得headers
     *
     * @return array
     */
    public function getHeaders() {

        return $this->_headers;
    }

    /**
     * clearHeaders
     * 清空headers
     *
     * @return Controller_Response
     */
    public function clearHeaders() {

        $this->_headers = array();
        return $this;
    }

    /**
     * clearHeader
     * 清空某个header
     *
     * @param  string $name
     * @return Controller_Response
     */
    public function clearHeader($name) {

        if (! count($this->_headers)) {
            return $this;
        }

        foreach ($this->_headers as $index => $header) {
            if ($name == $header['name']) {
                unset($this->_headers[$index]);
            }
        }

        return $this;
    }

    /**
     * Set raw HTTP header
     * 设置一个源字符串数据的header例如：setHeader("Content-Type: text/html")
     *
     * @param string $value
     * @return Controller_Response
     */
    public function setRawHeader($value) {

        $this->canSendHeaders(true);
        if ('Location' == substr($value, 0, 8)) {
            $this->_isRedirect = true;
        }
        $this->_headersRaw[] = (string) $value;
        return $this;
    }

    /**
     * 获取使用源数据设置的header
     *
     * @return array
     */
    public function getRawHeaders() {

        return $this->_headersRaw;
    }

    /**
     * 清空所有使用源数据设置的header
     *
     * @return Controller_Response
     */
    public function clearRawHeaders() {

        $this->_headersRaw = array();
        return $this;
    }

    /**
     * 清空某个使用源数据设置的header
     *
     * @param  string $headerRaw
     * @return Controller_Response
     */
    public function clearRawHeader($headerRaw) {

        if (! count($this->_headersRaw)) {
            return $this;
        }

        $key = array_search($headerRaw, $this->_headersRaw);
        unset($this->_headersRaw[$key]);

        return $this;
    }

    /**
     * 清空所有header
     *
     * @return Controller_Response
     */
    public function clearAllHeaders() {

        return $this->clearHeaders()
                    ->clearRawHeaders();
    }

    /**
     * 设置HTTP code
     *
     * @param int $code
     * @return Controller_Response
     */
    public function setHttpResponseCode($code) {

        if (!is_int($code) || (100 > $code) || (599 < $code)) {
            throw new Controller_Exception('Invalid HTTP response code');
        }

        if ((300 <= $code) && (307 >= $code)) {
            $this->_isRedirect = true;
        } else {
            $this->_isRedirect = false;
        }

        $this->_httpResponseCode = $code;
        return $this;
    }

    /**
     * 获取响应的HTTP code
     *
     * @return int
     */
    public function getHttpResponseCode() {

        return $this->_httpResponseCode;
    }

    /**
     * 判断是否可以发送header
     *
     * @param boolean $throw 如果header已经发送，是否抛出异常
     * @return boolean
     * @throws Controller_Exception
     */
    public function canSendHeaders($throw = false) {

        $ok = headers_sent($file, $line);
        if ($ok && $throw && $this->headersSentThrowsException) {
            throw new Controller_Exception('Cannot send headers; headers already sent in ' . $file . ', line ' . $line);
        }

        return !$ok;
    }

    /**
     * sendHeaders
     * 发送headers
     *
     * @return Controller_Response
     */
    public function sendHeaders() {

        // Only check if we can send headers if we have headers to send
        if (count($this->_headersRaw) || count($this->_headers) || (200 != $this->_httpResponseCode)) {
            $this->canSendHeaders(true);
        } elseif (200 == $this->_httpResponseCode) {
            // Haven't changed the response code, and we have no headers
            return $this;
        }

        $httpCodeSent = false;

        foreach ($this->_headersRaw as $header) {
            if (!$httpCodeSent && $this->_httpResponseCode) {
                header($header, true, $this->_httpResponseCode);
                $httpCodeSent = true;
            } else {
                header($header);
            }
        }

        foreach ($this->_headers as $header) {
            if (!$httpCodeSent && $this->_httpResponseCode) {
                header($header['name'] . ': ' . $header['value'], $header['replace'], $this->_httpResponseCode);
                $httpCodeSent = true;
            } else {
                header($header['name'] . ': ' . $header['value'], $header['replace']);
            }
        }

        if (!$httpCodeSent) {
            header('HTTP/1.1 ' . $this->_httpResponseCode);
            $httpCodeSent = true;
        }

        return $this;
    }

    /**
     * setBody
     * 设置响应体的内容
     *
     * If $name is not passed, or is not a string, resets the entire body and
     * sets the 'default' key to $content.
     *
     * If $name is a string, sets the named segment in the body array to
     * $content.
     *
     * @param string $content
     * @param null|string $name
     * @return Controller_Response
     */
    public function setBody($content, $name = null) {

        if ((null === $name) || !is_string($name)) {
            $this->_body = array('default' => (string) $content);
        } else {
            $this->_body[$name] = (string) $content;
        }

        return $this;
    }

    /**
     * Append content to the body content
     * 附加响应体内容
     *
     * @param string $content
     * @param null|string $name
     * @return Controller_Response
     */
    public function appendBody($content, $name = null) {

        if ((null === $name) || !is_string($name)) {
            if (isset($this->_body['default'])) {
                $this->_body['default'] .= (string) $content;
            } else {
                return $this->append('default', $content);
            }
        } elseif (isset($this->_body[$name])) {
            $this->_body[$name] .= (string) $content;
        } else {
            return $this->append($name, $content);
        }

        return $this;
    }

    /**
     * 清空响应体
     *
     * With no arguments, clears the entire body array. Given a $name, clears
     * just that named segment; if no segment matching $name exists, returns
     * false to indicate an error.
     *
     * @param  string $name Named segment to clear
     * @return boolean
     */
    public function clearBody($name = null) {

        if (null !== $name) {
            $name = (string) $name;
            if (isset($this->_body[$name])) {
                unset($this->_body[$name]);
                return true;
            }

            return false;
        }

        $this->_body = array();
        return true;
    }

    /**
     * 获取响应内容
     *
     * If $spec is false, returns the concatenated values of the body content
     * array. If $spec is boolean true, returns the body content array. If
     * $spec is a string and matches a named segment, returns the contents of
     * that segment; otherwise, returns null.
     *
     * @param boolean $spec
     * @return string|array|null
     */
    public function getBody($spec = false) {

        if (false === $spec) {
            ob_start();
            $this->outputBody();
            return ob_get_clean();
        } elseif (true === $spec) {
            return $this->_body;
        } elseif (is_string($spec) && isset($this->_body[$spec])) {
            return $this->_body[$spec];
        }

        return null;
    }

    /**
     * 在最后附加响应体
     *
     * If segment already exists, replaces with $content and places at end of
     * array.
     *
     * @param string $name
     * @param string $content
     * @return Controller_Response
     */
    public function append($name, $content) {

        if (!is_string($name)) {
            throw new Controller_Exception('Invalid body segment key ("' . gettype($name) . '")');
        }

        if (isset($this->_body[$name])) {
            unset($this->_body[$name]);
        }
        $this->_body[$name] = (string) $content;
        return $this;
    }

    /**
     * 在最前附加响应体
     *
     * If segment already exists, replaces with $content and places at top of
     * array.
     *
     * @param string $name
     * @param string $content
     * @return void
     */
    public function prepend($name, $content) {

        if (!is_string($name)) {
            throw new Controller_Exception('Invalid body segment key ("' . gettype($name) . '")');
        }

        if (isset($this->_body[$name])) {
            unset($this->_body[$name]);
        }

        $new = array($name => (string) $content);
        $this->_body = $new + $this->_body;

        return $this;
    }

    /**
     * 向整个响应中插入数据
     *
     * @param  string $name
     * @param  string $content
     * @param  string $parent
     * @param  boolean $before Whether to insert the new segment before or
     * after the parent. Defaults to false (after)
     * @return Controller_Response
     */
    public function insert($name, $content, $parent = null, $before = false) {

        if (!is_string($name)) {
            throw new Controller_Exception('Invalid body segment key ("' . gettype($name) . '")');
        }

        if ((null !== $parent) && !is_string($parent)) {
            throw new Controller_Exception('Invalid body segment parent key ("' . gettype($parent) . '")');
        }

        if (isset($this->_body[$name])) {
            unset($this->_body[$name]);
        }

        if ((null === $parent) || !isset($this->_body[$parent])) {
            return $this->append($name, $content);
        }

        $ins  = array($name => (string) $content);
        $keys = array_keys($this->_body);
        $loc  = array_search($parent, $keys);
        if (!$before) {
            // Increment location if not inserting before
            ++$loc;
        }

        if (0 === $loc) {
            // If location of key is 0, we're prepending
            $this->_body = $ins + $this->_body;
        } elseif ($loc >= (count($this->_body))) {
            // If location of key is maximal, we're appending
            $this->_body = $this->_body + $ins;
        } else {
            // Otherwise, insert at location specified
            $pre  = array_slice($this->_body, 0, $loc);
            $post = array_slice($this->_body, $loc);
            $this->_body = $pre + $ins + $post;
        }

        return $this;
    }

    /**
     * 输出响应体内容
     *
     * @return void
     */
    public function outputBody() {

        $body = implode('', $this->_body);
        echo $body;
    }

    /**
     * outputImage
     *
     * @param  mixed $file 
     * @return void
     */
    public function outputImage($file) {

        if ($_responseSent) {
            return false;
        }

        $this->sendHeaders();

        if (is_readable($file)) {
            readfile($file);
        }

        $this->_responseSent = true;
    }

    /**
     * 输出响应
     *
     * @return void
     */
    public function sendResponse() {

        if ($this->_responseSent) {
            return false;
        }

        $this->sendHeaders();
        $this->outputBody();
        $this->_responseSent = true;
    }

    public function stop() {

        $this->sendResponse();
        exit;
    }

    /**
     * 允许直接输出响应
     *
     * @return string
     */
    public function __toString() {

        ob_start();
        $this->sendResponse();
        return ob_get_clean();
    }
}
